<script setup>
import { computed, nextTick, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useLibraryStore, libraryConstants } from '@/stores/library'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import draggable from 'vuedraggable'

const router = useRouter()
const store = useLibraryStore()
const userStore = useUserStore()

const searchTerm = ref('')
const selectedSlot = ref(null)
const slotDialogVisible = ref(false)
const newPlacement = reactive({ code: '', note: '', name: '' })
const slotFeedback = ref(null)
const globalMessage = ref(null)
const editDialogVisible = ref(false)
const editingJournal = reactive({ 
  code: '', 
  name: '', 
  note: '', 
  originalCode: '',
  shelfId: '',
  sideId: '',
  level: null,
  placementIndex: 0
})
const moveConfirmVisible = ref(false)
const moveConfirmData = ref(null)
const editFeedback = ref(null)
const exportDialogVisible = ref(false)
const copySuccess = ref(false)
const importDialogVisible = ref(false)
const importJsonText = ref('')
const importFeedback = ref(null)

const sideLabelMap = libraryConstants.SIDES.reduce((acc, side) => {
  acc[side.id] = side.label
  return acc
}, {})

const slotKey = (shelfId, sideId, level) => `${shelfId}|${sideId}|${level}`

const trimmedSearch = computed(() => searchTerm.value.trim())

const searchResult = computed(() => {
  if (!trimmedSearch.value) return null
  return store.findPlacement(trimmedSearch.value)
})

const searchResults = computed(() => {
  if (!trimmedSearch.value) return []
  return store.findPlacements(trimmedSearch.value)
})

const highlightKey = computed(() => {
  const results = searchResults.value
  if (results.length === 0) return null
  const selectedResult = results[selectedSearchResultIndex.value] || results[0]
  return slotKey(selectedResult.shelfId, selectedResult.sideId, selectedResult.level)
})

const selectedSearchResultIndex = ref(0)

const searchSuggestions = computed(() => {
  if (!trimmedSearch.value) return []
  const normalized = trimmedSearch.value.toUpperCase()
  const suggestions = store.searchCodes(trimmedSearch.value)
  return suggestions.filter((code) => code !== normalized).slice(0, 8)
})

const shelfMap = computed(() => {
  const map = new Map()
  store.layout.forEach((row) => {
    row.shelves.forEach((shelf) => {
      map.set(shelf.id, shelf)
    })
  })
  return map
})

const layoutWithAssignments = computed(() =>
  store.layout.map((row) => ({
    ...row,
    shelves: row.shelves.map((shelf) => ({
      ...shelf,
      sides: shelf.sides.map((side) => ({
        ...side,
        levels: side.levels.map((levelNumber) => ({
          number: levelNumber,
          codes: store.listCodesForSlot(shelf.id, side.id, levelNumber),
          key: slotKey(shelf.id, side.id, levelNumber),
        })),
      })),
    })),
  })),
)

const rowOneShelves = computed(() => {
  const row = layoutWithAssignments.value.find((item) => item.index === 1)
  const shelves = row ? row.shelves : []
  if (focusedShelfId.value) {
    return shelves.filter((shelf) => shelf.id === focusedShelfId.value)
  }
  return shelves
})

const rowTwoShelves = computed(() => {
  const row = layoutWithAssignments.value.find((item) => item.index === 2)
  const shelves = row ? row.shelves : []
  if (focusedShelfId.value) {
    return shelves.filter((shelf) => shelf.id === focusedShelfId.value)
  }
  return shelves
})

const hiddenRowOneShelves = computed(() => {
  if (!focusedShelfId.value) return []
  const row = layoutWithAssignments.value.find((item) => item.index === 1)
  const shelves = row ? row.shelves : []
  return shelves
    .filter((shelf) => shelf.id !== focusedShelfId.value)
    .sort((a, b) => a.column - b.column)
})

const hiddenRowTwoShelves = computed(() => {
  if (!focusedShelfId.value) return []
  const row = layoutWithAssignments.value.find((item) => item.index === 2)
  const shelves = row ? row.shelves : []
  return shelves
    .filter((shelf) => shelf.id !== focusedShelfId.value)
    .sort((a, b) => a.column - b.column)
})

const mergedRowOneShelves = computed(() => {
  if (!focusedShelfId.value) {
    return rowOneShelves.value.map((shelf) => ({ type: 'shelf', shelf }))
  }
  const row = layoutWithAssignments.value.find((item) => item.index === 1)
  const allShelves = row ? [...row.shelves].sort((a, b) => a.column - b.column) : []
  return allShelves.map((shelf) => {
    if (shelf.id === focusedShelfId.value) {
      return { type: 'shelf', shelf }
    }
    return { type: 'placeholder', shelf }
  })
})

const mergedRowTwoShelves = computed(() => {
  if (!focusedShelfId.value) {
    return rowTwoShelves.value.map((shelf) => ({ type: 'shelf', shelf }))
  }
  const row = layoutWithAssignments.value.find((item) => item.index === 2)
  const allShelves = row ? [...row.shelves].sort((a, b) => a.column - b.column) : []
  return allShelves.map((shelf) => {
    if (shelf.id === focusedShelfId.value) {
      return { type: 'shelf', shelf }
    }
    return { type: 'placeholder', shelf }
  })
})

const selectedSlotKey = computed(() => {
  if (!selectedSlot.value) return null
  return slotKey(selectedSlot.value.shelfId, selectedSlot.value.sideId, selectedSlot.value.level)
})

const selectedSlotCodes = computed(() => {
  if (!selectedSlot.value) return []
  return store.listCodesForSlot(selectedSlot.value.shelfId, selectedSlot.value.sideId, selectedSlot.value.level)
})

const draggableCodes = ref([])
const focusedShelfId = ref(null)
const focusedSideId = ref(null)

const formatLocation = (placement) => {
  if (!placement) return ''
  const shelf = shelfMap.value.get(placement.shelfId)
  const shelfName = shelf ? shelf.name : placement.shelfId
  const sideLabel = sideLabelMap[placement.sideId] || placement.sideId
  return `${shelfName} · ${sideLabel} · 第${placement.level}层`
}

const openSlotDialog = (shelf, side, level) => {
  selectedSlot.value = {
    shelfId: shelf.id,
    shelfName: shelf.name,
    sideId: side.id,
    sideLabel: side.label,
    level,
  }
  newPlacement.code = ''
  newPlacement.note = ''
  newPlacement.name = ''
  slotFeedback.value = null
  draggableCodes.value = [...selectedSlotCodes.value]
  slotDialogVisible.value = true
}

const handleDragEnd = async () => {
  if (!selectedSlot.value) return
  
  // 检查登录状态
  if (!userStore.isLoggedIn) {
    ElMessage.warning('请先登录以调整排序')
    router.push('/login')
    return
  }
  
  await store.updateSlotOrder(
    selectedSlot.value.shelfId,
    selectedSlot.value.sideId,
    selectedSlot.value.level,
    draggableCodes.value
  )
}

const closeSlotDialog = () => {
  slotDialogVisible.value = false
}

const addJournalToSlot = async () => {
  if (!selectedSlot.value) return

  // 检查登录状态
  if (!userStore.isLoggedIn) {
    ElMessage.warning('请先登录以添加期刊')
    router.push('/login')
    return
  }

  const result = await store.addPlacement(newPlacement.code, {
    shelfId: selectedSlot.value.shelfId,
    sideId: selectedSlot.value.sideId,
    level: selectedSlot.value.level,
    note: newPlacement.note,
    name: newPlacement.name,
  }, false)

  if (!result.success) {
    if (result.reason === 'exists') {
      // 显示确认对话框，提供选项：移动到其他位置或添加到当前位置
      moveConfirmData.value = {
        code: newPlacement.code,
        existing: result.existing,
        target: {
          shelfId: selectedSlot.value.shelfId,
          sideId: selectedSlot.value.sideId,
          level: selectedSlot.value.level,
          shelfName: selectedSlot.value.shelfName,
          sideLabel: selectedSlot.value.sideLabel,
        },
        note: newPlacement.note,
        name: newPlacement.name,
      }
      moveConfirmVisible.value = true
      return
    }
    slotFeedback.value = {
      type: 'error',
      message: result.message || '请输入有效的期刊编号（如 A6/27）。',
    }
    return
  }

  if (result.updated) {
    slotFeedback.value = {
      type: 'success',
      message: '期刊备注已更新。',
    }
    // 重新获取数据以确保数据同步
    await store.fetchPlacements()
  } else if (result.added) {
    slotFeedback.value = {
      type: 'success',
      message: '期刊已成功记录到此位置。',
    }
    // 重新获取数据以确保新添加的数据有正确的id
    await store.fetchPlacements()
  }

  newPlacement.code = ''
  newPlacement.note = ''
  newPlacement.name = ''
  draggableCodes.value = [...selectedSlotCodes.value]
}

const confirmMove = async () => {
  if (!moveConfirmData.value) return

  const existingArray = Array.isArray(moveConfirmData.value.existing) 
    ? moveConfirmData.value.existing 
    : [moveConfirmData.value.existing]
  
  // 先删除所有旧位置
  for (const existing of existingArray) {
    await store.removePlacement(
      moveConfirmData.value.code,
      existing.shelfId,
      existing.sideId,
      existing.level
    )
  }

  // 然后添加到新位置
  const result = await store.addPlacement(moveConfirmData.value.code, {
    shelfId: moveConfirmData.value.target.shelfId,
    sideId: moveConfirmData.value.target.sideId,
    level: moveConfirmData.value.target.level,
    note: moveConfirmData.value.note,
    name: moveConfirmData.value.name,
  }, true)

  if (result.success) {
    if (existingArray.length === 1) {
      slotFeedback.value = {
        type: 'success',
        message: `期刊已从 ${formatLocation(existingArray[0])} 移动到新位置。`,
      }
    } else {
      const locations = existingArray.map(e => formatLocation(e)).join('、')
      slotFeedback.value = {
        type: 'success',
        message: `期刊已从 ${locations} 移动到新位置。`,
      }
    }
    // 重新获取数据以确保新移动的数据有正确的id
    await store.fetchPlacements()
    newPlacement.code = ''
    newPlacement.note = ''
    newPlacement.name = ''
    draggableCodes.value = [...selectedSlotCodes.value]
  } else {
    slotFeedback.value = {
      type: 'error',
      message: result.message || '移动失败，请重试。',
    }
  }

  moveConfirmVisible.value = false
  moveConfirmData.value = null
}

const confirmAddDuplicate = async () => {
  if (!moveConfirmData.value) return

  const result = await store.addPlacement(moveConfirmData.value.code, {
    shelfId: moveConfirmData.value.target.shelfId,
    sideId: moveConfirmData.value.target.sideId,
    level: moveConfirmData.value.target.level,
    note: moveConfirmData.value.note,
    name: moveConfirmData.value.name,
  }, true)

  if (result.success) {
    const existingArray = Array.isArray(moveConfirmData.value.existing) 
      ? moveConfirmData.value.existing 
      : [moveConfirmData.value.existing]
    
    slotFeedback.value = {
      type: 'success',
      message: `期刊已添加到当前位置（该编号现存在于 ${existingArray.length + 1} 个位置）。`,
    }
    // 重新获取数据以确保新添加的数据有正确的id
    await store.fetchPlacements()
    newPlacement.code = ''
    newPlacement.note = ''
    newPlacement.name = ''
    draggableCodes.value = [...selectedSlotCodes.value]
  } else {
    slotFeedback.value = {
      type: 'error',
      message: result.message || '添加失败，请重试。',
    }
  }

  moveConfirmVisible.value = false
  moveConfirmData.value = null
}

const cancelMove = () => {
  moveConfirmVisible.value = false
  moveConfirmData.value = null
}

const removeJournalFromSlot = async (code) => {
  if (!selectedSlot.value) return

  // 检查登录状态
  if (!userStore.isLoggedIn) {
    ElMessage.warning('请先登录以删除期刊')
    router.push('/login')
    return
  }
  
  const ok = await store.removePlacement(
    code,
    selectedSlot.value.shelfId,
    selectedSlot.value.sideId,
    selectedSlot.value.level
  )
  slotFeedback.value = ok
    ? { type: 'success', message: `期刊 ${code} 已从此位置移除。` }
    : { type: 'error', message: '未找到该期刊的记录。' }
  if (ok) {
    draggableCodes.value = draggableCodes.value.filter((c) => c !== code)
  }
}

const openEditDialog = (code) => {
  // 检查登录状态
  if (!userStore.isLoggedIn) {
    ElMessage.warning('请先登录以编辑期刊')
    router.push('/login')
    return
  }

  const placements = store.findPlacements(code)
  if (!placements || placements.length === 0) return
  
  // 如果该编号有多个位置，优先选择当前槽位的位置
  let placement = placements[0]
  if (selectedSlot.value) {
    const currentPlacement = placements.find(
      p => p.shelfId === selectedSlot.value.shelfId &&
           p.sideId === selectedSlot.value.sideId &&
           p.level === selectedSlot.value.level
    )
    if (currentPlacement) {
      placement = currentPlacement
    }
  }
  
  editingJournal.originalCode = code
  editingJournal.code = code
  editingJournal.name = placement.bookName || ''
  editingJournal.note = placement.note || ''
  editingJournal.shelfId = placement.shelfId
  editingJournal.sideId = placement.sideId
  editingJournal.level = placement.level
  editingJournal.placementIndex = placements.indexOf(placement)
  editFeedback.value = null
  editDialogVisible.value = true
}

const closeEditDialog = () => {
  editDialogVisible.value = false
  editingJournal.code = ''
  editingJournal.name = ''
  editingJournal.note = ''
  editingJournal.originalCode = ''
  editingJournal.shelfId = ''
  editingJournal.sideId = ''
  editingJournal.level = null
  editingJournal.placementIndex = 0
  editFeedback.value = null
}

const updateJournal = async () => {
  if (!editingJournal.code || !editingJournal.code.trim()) {
    editFeedback.value = {
      type: 'error',
      message: '请输入有效的期刊编号。',
    }
    return
  }

  if (!editingJournal.shelfId || !editingJournal.sideId || !editingJournal.level) {
    editFeedback.value = {
      type: 'error',
      message: '请选择书架、面和层数。',
    }
    return
  }

  const result = await store.updatePlacement(editingJournal.originalCode, {
    id: editingJournal.id,
    code: editingJournal.code,
    name: editingJournal.name,
    note: editingJournal.note,
    shelfId: editingJournal.shelfId,
    sideId: editingJournal.sideId,
    level: editingJournal.level,
    placementIndex: editingJournal.placementIndex,
  })

  if (!result.success) {
    if (result.reason === 'duplicate') {
      editFeedback.value = {
        type: 'error',
        message: '该期刊编号已存在，请使用其他编号。',
      }
    } else if (result.reason === 'empty-code') {
      editFeedback.value = {
        type: 'error',
        message: '请输入有效的期刊编号。',
      }
    } else {
      editFeedback.value = {
        type: 'error',
        message: result.message || '更新失败，请重试。',
      }
    }
    return
  }

  editFeedback.value = {
    type: 'success',
    message: '期刊信息已更新。',
  }

  // 如果编号改变了，需要更新draggableCodes
  const normalizedOldCode = editingJournal.originalCode.trim().toUpperCase()
  const normalizedNewCode = editingJournal.code.trim().toUpperCase()
  if (normalizedOldCode !== normalizedNewCode) {
    const index = draggableCodes.value.findIndex(c => c.toUpperCase() === normalizedOldCode)
    if (index !== -1) {
      draggableCodes.value[index] = normalizedNewCode
      await handleDragEnd()
    }
  }

  // 延迟关闭对话框，让用户看到成功消息
  setTimeout(() => {
    closeEditDialog()
    // 刷新draggableCodes以反映更新
    draggableCodes.value = [...selectedSlotCodes.value]
  }, 1000)
}

const clearSearch = () => {
  searchTerm.value = ''
  globalMessage.value = null
  focusedShelfId.value = null
  focusedSideId.value = null
}

const handleLogout = async () => {
  await userStore.userLogout()
  ElMessage.success('已退出登录')
  router.push('/login')
}

const locateShelf = () => {
  const results = searchResults.value
  if (results.length === 0) return
  
  const selectedResult = results[selectedSearchResultIndex.value] || results[0]
  focusedShelfId.value = selectedResult.shelfId
  focusedSideId.value = selectedResult.sideId
  
  // 滚动到对应的书架
  nextTick(() => {
    const shelfElement = document.querySelector(`[data-shelf-id="${selectedResult.shelfId}"]`)
    if (shelfElement) {
      shelfElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
    }
  })
}

const resetFocus = () => {
  focusedShelfId.value = null
  focusedSideId.value = null
}

const applySuggestion = (code) => {
  searchTerm.value = code
}

const exportData = computed(() => {
  return JSON.stringify(store.placements, null, 2)
})

const openExportDialog = () => {
  exportDialogVisible.value = true
  copySuccess.value = false
}

const closeExportDialog = () => {
  exportDialogVisible.value = false
  copySuccess.value = false
}

const copyToClipboard = async () => {
  try {
    await navigator.clipboard.writeText(exportData.value)
    copySuccess.value = true
    setTimeout(() => {
      copySuccess.value = false
    }, 2000)
  } catch (error) {
    console.error('复制失败:', error)
    // 降级方案：使用传统方法
    const textArea = document.createElement('textarea')
    textArea.value = exportData.value
    textArea.style.position = 'fixed'
    textArea.style.opacity = '0'
    document.body.appendChild(textArea)
    textArea.select()
    try {
      document.execCommand('copy')
      copySuccess.value = true
      setTimeout(() => {
        copySuccess.value = false
      }, 2000)
    } catch (err) {
      console.error('复制失败:', err)
    }
    document.body.removeChild(textArea)
  }
}

const openImportDialog = () => {
  importDialogVisible.value = true
  importJsonText.value = ''
  importFeedback.value = null
}

const closeImportDialog = () => {
  importDialogVisible.value = false
  importJsonText.value = ''
  importFeedback.value = null
}

const handleImport = () => {
  if (!importJsonText.value.trim()) {
    importFeedback.value = {
      type: 'error',
      message: '请输入要导入的JSON数据',
    }
    return
  }

  const result = store.importPlacements(importJsonText.value.trim())

  if (result.success) {
    importFeedback.value = {
      type: 'success',
      message: `成功导入 ${result.count} 条期刊数据`,
    }
    // 延迟关闭对话框，让用户看到成功消息
    setTimeout(() => {
      closeImportDialog()
    }, 1500)
  } else {
    importFeedback.value = {
      type: 'error',
      message: result.message || '导入失败，请检查数据格式',
    }
  }
}

const syncGlobalMessage = () => {
  if (!trimmedSearch.value) {
    globalMessage.value = null
    return
  }

  const results = searchResults.value
  if (results.length > 0) {
    if (results.length === 1) {
      const location = formatLocation(results[0])
      const journalName = results[0].bookName || ''
      const message = journalName 
        ? `已找到：${location} · ${journalName}`
        : `已找到：${location}`
      globalMessage.value = {
        type: 'success',
        message,
      }
    } else {
      // 多个位置
      const journalName = results[0].bookName || ''
      const message = journalName 
        ? `已找到 ${results.length} 个位置 · ${journalName}（请选择）`
        : `已找到 ${results.length} 个位置（请选择）`
      globalMessage.value = {
        type: 'success',
        message,
      }
      // 默认选择第一个
      if (selectedSearchResultIndex.value >= results.length) {
        selectedSearchResultIndex.value = 0
      }
    }
  } else {
    globalMessage.value = {
      type: 'error',
      message: '没有找到对应的期刊位置，试试检查编号是否正确。',
    }
  }
}

watch(trimmedSearch, () => {
  selectedSearchResultIndex.value = 0
  syncGlobalMessage()
}, { immediate: true })

watch(slotDialogVisible, (visible) => {
  if (!visible) {
    selectedSlot.value = null
    slotFeedback.value = null
  } else if (selectedSlot.value) {
    draggableCodes.value = [...selectedSlotCodes.value]
  }
})

watch(selectedSlotCodes, (newCodes) => {
  if (slotDialogVisible.value && selectedSlot.value) {
    draggableCodes.value = [...newCodes]
  }
}, { deep: true })
</script>

<template>
  <main class="page">
    <header class="page-header">
      <div class="page-header__top">
        <div>
          <h1>期刊上架检索系统</h1>
          <p>
            输入期刊编号即可定位到书架、层数与面向；点击书架某一层即可为其分配期刊，实现快速上架与调整。
          </p>
        </div>
        <div style="display: flex; gap: 12px; align-items: center;">
          <el-button v-if="store.loading" type="info" disabled>
            <span style="margin-right: 6px">⏳</span>
            加载中...
          </el-button>
          <el-button v-else type="primary" @click="store.fetchPlacements">
            <span style="margin-right: 6px">🔄</span>
            刷新数据
          </el-button>
          <el-button type="primary" @click="openImportDialog">
            <span style="margin-right: 6px">📤</span>
            导入数据
          </el-button>
          <el-button type="primary" @click="openExportDialog">
            <span style="margin-right: 6px">📥</span>
            导出数据
          </el-button>
          <el-button v-if="userStore.isLoggedIn" type="success" @click="handleLogout">
            <span style="margin-right: 6px">👤</span>
            {{ userStore.userInfo?.nickName || userStore.userInfo?.userName || '已登录' }}
          </el-button>
          <el-button v-else type="warning" @click="router.push('/login')">
            <span style="margin-right: 6px">🔐</span>
            登录
          </el-button>
        </div>
      </div>
    </header>

    <el-alert
      v-if="store.error"
      :title="store.error"
      type="error"
      :closable="true"
      @close="store.error = null"
      style="margin-bottom: 20px;"
    />

    <el-card class="search-panel" shadow="hover">
      <label class="search-label" for="journal-search">期刊编号检索</label>
      <div class="search-input-group">
        <el-input
          id="journal-search"
          v-model="searchTerm"
          type="search"
          placeholder="例如：A6/27"
          size="large"
          clearable
          @clear="clearSearch"
        />
        <el-button v-if="trimmedSearch" type="primary" plain @click="clearSearch">清空</el-button>
      </div>

      <div v-if="globalMessage" class="search-feedback-wrapper">
        <div style="flex: 1; display: flex; flex-direction: column; gap: 8px;">
          <p :class="['search-feedback', `is-${globalMessage.type}`]">
            {{ globalMessage.message }}
          </p>
          <div v-if="searchResults.length > 1" style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;">
            <span style="font-size: 13px; color: #60708f;">选择位置：</span>
            <el-radio-group v-model="selectedSearchResultIndex" size="small">
              <el-radio-button
                v-for="(result, index) in searchResults"
                :key="index"
                :label="index"
              >
                {{ formatLocation(result) }}
              </el-radio-button>
            </el-radio-group>
          </div>
        </div>
        <el-button
          v-if="searchResults.length > 0 && globalMessage.type === 'success'"
          type="primary"
          size="small"
          @click="focusedShelfId ? resetFocus() : locateShelf()"
        >
          <span v-if="focusedShelfId">✕</span>
          <span v-else>📍</span>
          {{ focusedShelfId ? '取消定位' : '定位书架' }}
        </el-button>
      </div>

      <ul v-if="searchSuggestions.length" class="suggestion-list">
        <li
          v-for="code in searchSuggestions"
          :key="code"
          class="suggestion-item"
        >
          <el-button size="small" round @click="applySuggestion(code)">{{ code }}</el-button>
        </li>
      </ul>
    </el-card>

    <section class="shelf-section">
      <div class="section-header">
        <h2>书架总览</h2>
        <div class="section-header__content">
          <p>共两排，每排五组书架，每组包含正反两面与六层。</p>
          <span class="section-header__desk">
            <span class="section-header__desk-icon">🖥️</span>
            <span>值班台</span>
          </span>
        </div>
      </div>
      <el-row class="shelf-double-layout" :gutter="24" justify="space-between">
        <el-col
          class="shelf-stack__col"
          :xs="8"
          :sm="9"
          :md="8"
          :lg="8"
          :xl="7"
        >
          <div class="shelf-stack" data-row="1">
            <header class="shelf-stack__header">
              <h3>第 1 排</h3>
            </header>
            <div class="shelf-stack__body">
              <template v-for="item in mergedRowOneShelves" :key="item.shelf.id">
                <div
                  v-if="item.type === 'placeholder'"
                  class="shelf-placeholder"
                >
                  <span class="shelf-placeholder__text">{{ item.shelf.name }}</span>
                </div>
                <el-card
                  v-else
                  :data-shelf-id="item.shelf.id"
                  class="shelf-card"
                  :class="{ 'is-focused': focusedShelfId === item.shelf.id }"
                  shadow="hover"
                >
                <template #header>
                  <div class="shelf-card__header">
                    <span class="shelf-card__title">{{ item.shelf.name }}</span>
                  </div>
                </template>
                <div class="shelf-card__body">
                  <template v-for="side in item.shelf.sides" :key="side.id">
                    <!-- 正常显示的面 -->
                    <div
                      v-if="!focusedShelfId || focusedShelfId !== item.shelf.id || !focusedSideId || focusedSideId === side.id"
                      class="shelf-side"
                    >
                      <div class="shelf-side__label">{{ side.label }}</div>
                      <div class="shelf-side__levels">
                        <el-button
                          v-for="level in side.levels"
                          :key="level.key"
                          type="default"
                          :class="[
                            'shelf-level',
                            {
                              'has-journals': level.codes.length,
                              'is-highlighted': highlightKey === level.key,
                              'is-selected': selectedSlotKey === level.key,
                            },
                          ]"
                          @click="openSlotDialog(item.shelf, side, level.number)"
                        >
                          <div class="shelf-level__index">第 {{ level.number }} 层</div>
                          <ul v-if="level.codes.length" class="shelf-level__codes">
                            <li 
                              v-for="code in level.codes" 
                              :key="code"
                              :class="{ 'is-highlighted-code': trimmedSearch && code.toUpperCase() === trimmedSearch.toUpperCase() }"
                            >{{ code }}</li>
                          </ul>
                          <span v-else class="shelf-level__empty">未分配</span>
                        </el-button>
                      </div>
                    </div>
                    <!-- 折叠占位框 -->
                    <div
                      v-else-if="focusedShelfId === item.shelf.id && focusedSideId && focusedSideId !== side.id"
                      class="shelf-side shelf-side--collapsed"
                    >
                      <div class="shelf-side__label">{{ side.label }}</div>
                      <div class="shelf-side__collapsed-placeholder">
                        <div class="shelf-side__collapsed-icon">📦</div>
                        <div class="shelf-side__collapsed-text">{{ side.label }}已折叠</div>
                        <el-button
                          type="primary"
                          size="small"
                          link
                          @click="focusedSideId = null"
                        >
                          展开
                        </el-button>
                      </div>
                    </div>
                  </template>
                </div>
              </el-card>
              </template>
            </div>
          </div>
        </el-col>

        <el-col
          class="shelf-stack__col"
          :xs="8"
          :sm="9"
          :md="8"
          :lg="8"
          :xl="7"
        >
          <div class="shelf-stack" data-row="2">
            <header class="shelf-stack__header">
              <h3>第 2 排</h3>
            </header>
            <div class="shelf-stack__body">
              <template v-for="item in mergedRowTwoShelves" :key="item.shelf.id">
                <div
                  v-if="item.type === 'placeholder'"
                  class="shelf-placeholder"
                >
                  <span class="shelf-placeholder__text">{{ item.shelf.name }}</span>
                </div>
                <el-card
                  v-else
                  :data-shelf-id="item.shelf.id"
                  class="shelf-card"
                  :class="{ 'is-focused': focusedShelfId === item.shelf.id }"
                  shadow="hover"
                >
                <template #header>
                  <div class="shelf-card__header">
                    <span class="shelf-card__title">{{ item.shelf.name }}</span>
                  </div>
                </template>
                <div class="shelf-card__body">
                  <template v-for="side in item.shelf.sides" :key="side.id">
                    <!-- 正常显示的面 -->
                    <div
                      v-if="!focusedShelfId || focusedShelfId !== item.shelf.id || !focusedSideId || focusedSideId === side.id"
                      class="shelf-side"
                    >
                      <div class="shelf-side__label">{{ side.label }}</div>
                      <div class="shelf-side__levels">
                        <el-button
                          v-for="level in side.levels"
                          :key="level.key"
                          type="default"
                          :class="[
                            'shelf-level',
                            {
                              'has-journals': level.codes.length,
                              'is-highlighted': highlightKey === level.key,
                              'is-selected': selectedSlotKey === level.key,
                            },
                          ]"
                          @click="openSlotDialog(item.shelf, side, level.number)"
                        >
                          <div class="shelf-level__index">第 {{ level.number }} 层</div>
                          <ul v-if="level.codes.length" class="shelf-level__codes">
                            <li 
                              v-for="code in level.codes" 
                              :key="code"
                              :class="{ 'is-highlighted-code': trimmedSearch && code.toUpperCase() === trimmedSearch.toUpperCase() }"
                            >{{ code }}</li>
                          </ul>
                          <span v-else class="shelf-level__empty">未分配</span>
                        </el-button>
                      </div>
                    </div>
                    <!-- 折叠占位框 -->
                    <div
                      v-else-if="focusedShelfId === item.shelf.id && focusedSideId && focusedSideId !== side.id"
                      class="shelf-side shelf-side--collapsed"
                    >
                      <div class="shelf-side__label">{{ side.label }}</div>
                      <div class="shelf-side__collapsed-placeholder">
                        <div class="shelf-side__collapsed-icon">📦</div>
                        <div class="shelf-side__collapsed-text">{{ side.label }}已折叠</div>
                        <el-button
                          type="primary"
                          size="small"
                          link
                          @click="focusedSideId = null"
                        >
                          展开
                        </el-button>
                      </div>
                    </div>
                  </template>
                </div>
              </el-card>
              </template>
            </div>
          </div>
        </el-col>
      </el-row>
    </section>

    <el-dialog
      v-model="slotDialogVisible"
      width="520px"
      :show-close="false"
      append-to-body
      destroy-on-close
      class="slot-dialog"
    >
      <template #header>
        <div class="slot-dialog__header">
          <h2 v-if="selectedSlot">
            {{ selectedSlot.shelfName }} · {{ selectedSlot.sideLabel }} · 第 {{ selectedSlot.level }} 层
          </h2>
          <el-button type="primary" link @click="closeSlotDialog">关闭</el-button>
        </div>
      </template>

      <section class="slot-dialog__section">
        <h3>当前期刊</h3>
        <p v-if="!draggableCodes.length" class="empty-hint">暂未记录期刊。</p>
        <draggable
          v-else
          v-model="draggableCodes"
          class="code-list"
          :disabled="!userStore.isLoggedIn"
          handle=".drag-handle"
          @end="handleDragEnd"
        >
          <template #item="{ element: code }">
            <el-tag
              :key="code"
              effect="plain"
              type="info"
              :closable="userStore.isLoggedIn"
              class="draggable-tag"
              :class="{ 'no-drag': !userStore.isLoggedIn }"
              @close.stop="removeJournalFromSlot(code)"
            >
              <span v-if="userStore.isLoggedIn" class="drag-handle">⋮⋮</span>
              <span class="tag-content" @click.stop="openEditDialog(code)">{{ code }}</span>
            </el-tag>
          </template>
        </draggable>
        <p v-if="!userStore.isLoggedIn && draggableCodes.length" class="login-hint">
          <el-text type="warning">提示：登录后可以编辑、删除和调整排序</el-text>
        </p>
      </section>

      <section class="slot-dialog__section">
        <h3>添加期刊</h3>
        <el-form label-position="top" class="add-form" @submit.prevent>
          <el-form-item label="期刊编号">
            <el-input
              id="new-code"
              v-model="newPlacement.code"
              placeholder="请输入期刊编号"
              clearable
            />
          </el-form-item>

          <el-form-item label="期刊名称（可选）">
            <el-input
              id="new-name"
              v-model="newPlacement.name"
              placeholder="请输入期刊名称"
              clearable
            />
          </el-form-item>

          <el-form-item label="备注（可选）">
            <el-input
              id="new-note"
              v-model="newPlacement.note"
              type="textarea"
              :rows="2"
              placeholder="可记录期刊主题、年份等信息"
            />
          </el-form-item>

          <el-button type="primary" class="form-submit" @click="addJournalToSlot">添加到当前层</el-button>
        </el-form>

        <p v-if="slotFeedback" :class="['form-feedback', `is-${slotFeedback.type}`]">
          {{ slotFeedback.message }}
        </p>
      </section>
    </el-dialog>

    <el-dialog
      v-model="editDialogVisible"
      width="520px"
      :show-close="false"
      append-to-body
      destroy-on-close
      class="edit-dialog"
    >
      <template #header>
        <div class="slot-dialog__header">
          <h2>编辑期刊</h2>
          <el-button type="primary" link @click="closeEditDialog">关闭</el-button>
        </div>
      </template>

      <section class="slot-dialog__section">
        <el-form label-position="top" class="add-form" @submit.prevent>
          <el-form-item label="期刊编号">
            <el-input
              id="edit-code"
              v-model="editingJournal.code"
              placeholder="请输入期刊编号"
              clearable
            />
          </el-form-item>

          <el-form-item label="期刊名称（可选）">
            <el-input
              id="edit-name"
              v-model="editingJournal.name"
              placeholder="请输入期刊名称"
              clearable
            />
          </el-form-item>

          <el-form-item label="书架">
            <el-select
              v-model="editingJournal.shelfId"
              placeholder="请选择书架"
              style="width: 100%"
            >
              <el-option
                v-for="shelf in store.shelves"
                :key="shelf.id"
                :label="shelf.name"
                :value="shelf.id"
              />
            </el-select>
          </el-form-item>

          <el-form-item label="面">
            <el-select
              v-model="editingJournal.sideId"
              placeholder="请选择面"
              style="width: 100%"
            >
              <el-option
                v-for="side in libraryConstants.SIDES"
                :key="side.id"
                :label="side.label"
                :value="side.id"
              />
            </el-select>
          </el-form-item>

          <el-form-item label="层数">
            <el-select
              v-model="editingJournal.level"
              placeholder="请选择层数"
              style="width: 100%"
            >
              <el-option
                v-for="level in [6, 5, 4, 3, 2, 1]"
                :key="level"
                :label="`第 ${level} 层`"
                :value="level"
              />
            </el-select>
          </el-form-item>

          <el-form-item label="备注（可选）">
            <el-input
              id="edit-note"
              v-model="editingJournal.note"
              type="textarea"
              :rows="2"
              placeholder="可记录期刊主题、年份等信息"
            />
          </el-form-item>

          <el-button type="primary" class="form-submit" @click="updateJournal">保存修改</el-button>
        </el-form>

        <p v-if="editFeedback" :class="['form-feedback', `is-${editFeedback.type}`]">
          {{ editFeedback.message }}
        </p>
      </section>
    </el-dialog>

    <el-dialog
      v-model="moveConfirmVisible"
      width="480px"
      :show-close="false"
      append-to-body
      class="move-confirm-dialog"
    >
      <template #header>
        <div class="slot-dialog__header">
          <h2>确认移动期刊</h2>
        </div>
      </template>

      <section class="slot-dialog__section" v-if="moveConfirmData">
        <p style="font-size: 14px; color: #2d3b5d; margin: 0 0 16px 0;">
          期刊编号 <strong>{{ moveConfirmData.code }}</strong> 已存在于以下位置：
        </p>
        <div style="background: #f5f7fa; padding: 12px; border-radius: 8px; margin-bottom: 16px;">
          <div v-for="(existing, index) in (Array.isArray(moveConfirmData.existing) ? moveConfirmData.existing : [moveConfirmData.existing])" 
               :key="index"
               style="margin-bottom: 8px; padding: 8px; background: white; border-radius: 6px;">
            <p style="margin: 0; font-size: 14px; color: #60708f;">
              {{ formatLocation(existing) }}
            </p>
          </div>
        </div>
        <p style="font-size: 14px; color: #2d3b5d; margin: 0 0 16px 0;">
          请选择操作：
        </p>
        <div style="background: #f0f4ff; padding: 12px; border-radius: 8px; margin-bottom: 20px;">
          <p style="margin: 0 0 8px 0; font-size: 14px; color: #3f6ad8; font-weight: 600;">
            目标位置：{{ moveConfirmData.target.shelfName }} · {{ moveConfirmData.target.sideLabel }} · 第 {{ moveConfirmData.target.level }} 层
          </p>
        </div>
        <div style="display: flex; gap: 12px; justify-content: flex-end; flex-wrap: wrap;">
          <el-button @click="cancelMove">取消</el-button>
          <el-button type="warning" @click="confirmAddDuplicate">添加到当前位置（允许重复）</el-button>
          <el-button type="primary" @click="confirmMove">移动到新位置</el-button>
        </div>
      </section>
    </el-dialog>

    <el-dialog
      v-model="exportDialogVisible"
      width="600px"
      :show-close="false"
      append-to-body
      destroy-on-close
      class="export-dialog"
    >
      <template #header>
        <div class="slot-dialog__header">
          <h2>导出数据</h2>
          <el-button type="primary" link @click="closeExportDialog">关闭</el-button>
        </div>
      </template>

      <section class="slot-dialog__section">
        <div class="export-actions">
          <el-button type="primary" @click="copyToClipboard">
            <span v-if="copySuccess">✓ 已复制</span>
            <span v-else>📋 复制到剪贴板</span>
          </el-button>
        </div>
        <div class="json-display">
          <pre class="json-content">{{ exportData }}</pre>
        </div>
      </section>
    </el-dialog>

    <el-dialog
      v-model="importDialogVisible"
      width="600px"
      :show-close="false"
      append-to-body
      destroy-on-close
      class="import-dialog"
    >
      <template #header>
        <div class="slot-dialog__header">
          <h2>导入数据</h2>
          <el-button type="primary" link @click="closeImportDialog">关闭</el-button>
        </div>
      </template>

      <section class="slot-dialog__section">
        <p style="font-size: 13px; color: #60708f; margin: 0 0 12px 0;">
          请粘贴之前导出的JSON数据，导入后将替换当前所有期刊数据。
        </p>
        <el-input
          v-model="importJsonText"
          type="textarea"
          :rows="12"
          placeholder='请粘贴JSON数组数据，例如：[{"id": null, "classNo": "A1/29", "shelfId": "S1", "sideId": "A", "level": 6, "bookName": "", "note": "", "sortOrder": 0, "createTime": "2025-01-01T00:00:00.000Z", "updateTime": null, "createBy": null, "updateBy": null}]'
          class="import-textarea"
        />
        <div class="export-actions">
          <el-button type="primary" @click="handleImport">确认导入</el-button>
        </div>
        <p v-if="importFeedback" :class="['form-feedback', `is-${importFeedback.type}`]">
          {{ importFeedback.message }}
        </p>
      </section>
    </el-dialog>
  </main>
</template>

<style scoped>
.page {
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(16px, 4vw, 40px);
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 4vw, 40px);
}

.page-header {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.page-header__top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}

.page-header h1 {
  font-size: clamp(26px, 4vw, 34px);
  font-weight: 600;
  color: #1f2a44;
}

.page-header p {
  font-size: 15px;
  color: #4c5a73;
  max-width: 720px;
}

.search-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.search-label {
  font-weight: 600;
  color: #1f2a44;
}

.search-input-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

.search-input-group .el-input {
  flex: 1;
}

.search-feedback-wrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.search-feedback {
  padding: 10px 14px;
  border-radius: 10px;
  font-size: 14px;
  flex: 1;
  min-width: 200px;
}

.search-feedback.is-success {
  background: rgba(46, 204, 113, 0.12);
  color: #21744a;
}

.search-feedback.is-error {
  background: rgba(231, 76, 60, 0.12);
  color: #b63f2d;
}

.suggestion-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  padding: 0;
  margin: 0;
}

.shelf-section {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.section-header h2 {
  font-size: 22px;
  color: #1f2a44;
  font-weight: 600;
}

.section-header__content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}

.section-header p {
  font-size: 14px;
  color: #60708f;
  margin: 0;
  flex: 1;
}

.section-header__desk {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: #3f6ad8;
  font-weight: 600;
  white-space: nowrap;
  padding: 8px 14px;
  background: linear-gradient(135deg, rgba(63, 106, 216, 0.1) 0%, rgba(63, 106, 216, 0.05) 100%);
  border: 1.5px solid rgba(63, 106, 216, 0.3);
  border-radius: 8px;
  box-shadow: 0 2px 6px rgba(63, 106, 216, 0.15);
  transition: all 0.2s ease;
}

.section-header__desk:hover {
  background: linear-gradient(135deg, rgba(63, 106, 216, 0.15) 0%, rgba(63, 106, 216, 0.08) 100%);
  border-color: rgba(63, 106, 216, 0.5);
  box-shadow: 0 3px 8px rgba(63, 106, 216, 0.2);
  transform: translateY(-1px);
}

.section-header__desk-icon {
  font-size: 16px;
  line-height: 1;
}

.shelf-double-layout {
  width: 100%;
  align-items: stretch;
  flex-wrap: nowrap;
  overflow-x: auto;
  padding-bottom: 6px;
}

.shelf-double-layout::-webkit-scrollbar {
  height: 6px;
}

.shelf-double-layout::-webkit-scrollbar-thumb {
  background: rgba(31, 42, 68, 0.18);
  border-radius: 999px;
}



.shelf-stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
}

.shelf-stack__header h3 {
  font-size: 18px;
  font-weight: 600;
  color: #2f3a56;
}

.shelf-stack__body {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.shelf-walkway {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 16px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(239, 243, 255, 0.9) 100%);
  border-radius: 20px;
  border: 1px dashed rgba(63, 106, 216, 0.55);
  padding: 24px 12px;
  min-height: 100%;
  align-self: stretch;
  text-align: center;
  position: relative;
}

.walkway-wrapper {
  min-width: 150px;
  display: flex;
  align-items: stretch;
}

.shelf-walkway::before,
.shelf-walkway::after {
  content: '';
  width: 36px;
  height: 6px;
  border-radius: 999px;
  background: rgba(63, 106, 216, 0.35);
}

.shelf-walkway__label {
  font-size: 18px;
  font-weight: 600;
  color: #3f4d6b;
  letter-spacing: 3px;
}

.shelf-card {
  background: #ffffff;
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 18px 40px -32px rgba(31, 42, 68, 0.4);
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: all 0.3s ease;
}

.shelf-card.is-focused {
  box-shadow: 0 0 0 3px rgba(63, 106, 216, 0.3), 0 18px 40px -20px rgba(63, 106, 216, 0.4);
  transform: scale(1.02);
  border: 2px solid #3f6ad8;
}

.shelf-placeholder {
  background: #f5f7fa;
  border: 1px dashed #d4d8e3;
  border-radius: 12px;
  padding: 16px 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60px;
  transition: all 0.2s ease;
}

.shelf-placeholder:hover {
  background: #eef2f7;
  border-color: #b4bee0;
}

.shelf-placeholder__text {
  font-size: 14px;
  font-weight: 500;
  color: #909399;
  user-select: none;
}

.shelf-card__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.shelf-card__title {
  font-weight: 600;
  color: #1f2a44;
}

.shelf-card__body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow: visible;
}

.shelf-side {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.shelf-side__label {
  font-size: 13px;
  font-weight: 600;
  color: #3f6ad8;
}

.shelf-side--collapsed {
  opacity: 0.7;
}

.shelf-side__collapsed-placeholder {
  background: #f5f7fa;
  border: 1px dashed #d4d8e3;
  border-radius: 12px;
  padding: 24px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 120px;
  transition: all 0.2s ease;
}

.shelf-side__collapsed-placeholder:hover {
  background: #eef2f7;
  border-color: #b4bee0;
}

.shelf-side__collapsed-icon {
  font-size: 32px;
  opacity: 0.6;
}

.shelf-side__collapsed-text {
  font-size: 14px;
  color: #909399;
  font-weight: 500;
}

.shelf-side__levels {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 8px;
}

.shelf-card__body :deep(.shelf-level) {
  width: 100%;
  min-width: 0;
  justify-content: flex-start;
  align-items: flex-start;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid #d4d8e3;
  background: #f9fbff;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: all 0.2s ease;
  text-align: left;
  min-height: 88px;
  overflow: visible;
}

/* 手机竖屏时，确保容器可以水平滚动 */
@media (max-width: 768px) {
  .shelf-card__body :deep(.shelf-level) {
    overflow-x: auto;
    overflow-y: hidden;
  }
}

.shelf-card__body :deep(.shelf-level:hover) {
  border-color: #b4bee0;
  background: #f0f4ff;
  transform: translateY(-1px);
}

.shelf-card__body :deep(.shelf-level.has-journals) {
  border-color: rgba(63, 106, 216, 0.4);
  background: rgba(63, 106, 216, 0.08);
}

.shelf-card__body :deep(.shelf-level.is-highlighted) {
  border-color: #2ecc71;
  background: rgba(46, 204, 113, 0.18);
  box-shadow: 0 0 0 3px rgba(46, 204, 113, 0.25);
}

.shelf-card__body :deep(.shelf-level.is-selected) {
  border-color: #3f6ad8;
  box-shadow: 0 0 0 3px rgba(63, 106, 216, 0.2);
}

.shelf-level__index {
  font-size: 13px;
  font-weight: 600;
  color: #3f4d6b;
}

.shelf-level__codes {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 6px;
  width: fit-content;
  min-width: 100%;
  align-items: center;
}

/* 手机竖屏时，允许换行，最多显示3行，超出显示滚动条 */
@media (max-width: 768px) {
  .shelf-level__codes {
    flex-wrap: wrap;
    overflow-x: auto;
    overflow-y: auto;
    /* 3行高度：标签实际高度约 (font-size 13px * line-height 1.2 ≈ 15.6px) + padding上下8px ≈ 23.6px，加上gap 6px，每行约30px，3行约90px */
    max-height: calc(3 * (13px * 1.2 + 8px + 6px)); /* 3行：(字体高度 + padding + gap) */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: rgba(63, 106, 216, 0.3) transparent;
  }
  
  .shelf-level__codes::-webkit-scrollbar {
    width: 4px;
    height: 4px;
  }
  
  .shelf-level__codes::-webkit-scrollbar-thumb {
    background: rgba(63, 106, 216, 0.3);
    border-radius: 2px;
  }
  
  .shelf-level__codes::-webkit-scrollbar-track {
    background: transparent;
  }
  
  .shelf-level__codes li {
    flex-shrink: 0;
  }
}

.shelf-level__codes li {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(63, 106, 216, 0.12);
  color: #2d3b5d;
  font-size: 13px;
  line-height: 1.2;
  white-space: nowrap;
  transition: all 0.2s ease;
  height: fit-content;
}

.shelf-level__codes li.is-highlighted-code {
  background: rgba(46, 204, 113, 0.25);
  color: #21744a;
  font-weight: 600;
  box-shadow: 0 0 0 2px rgba(46, 204, 113, 0.4);
  transform: scale(1.05);
}

.shelf-level__empty {
  font-size: 12px;
  color: #98a2be;
}

:deep(.slot-dialog .el-dialog),
:deep(.edit-dialog .el-dialog),
:deep(.export-dialog .el-dialog),
:deep(.import-dialog .el-dialog),
:deep(.move-confirm-dialog .el-dialog) {
  border-radius: 18px;
  overflow: hidden;
}

:deep(.slot-dialog .el-dialog__header),
:deep(.edit-dialog .el-dialog__header),
:deep(.export-dialog .el-dialog__header),
:deep(.import-dialog .el-dialog__header),
:deep(.move-confirm-dialog .el-dialog__header) {
  margin: 0;
  padding: 20px 24px;
  background: #f5f7ff;
}

.slot-dialog__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.slot-dialog__header h2 {
  font-size: 18px;
  color: #1f2a44;
  margin: 0;
}

:deep(.slot-dialog .el-dialog__body),
:deep(.edit-dialog .el-dialog__body),
:deep(.export-dialog .el-dialog__body),
:deep(.import-dialog .el-dialog__body),
:deep(.move-confirm-dialog .el-dialog__body) {
  padding: 20px 24px 10px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.import-textarea :deep(.el-textarea__inner) {
  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.6;
}

.export-actions {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 8px;
}

.json-display {
  background: #f5f7fa;
  border: 1px solid #d4d8e3;
  border-radius: 8px;
  padding: 16px;
  max-height: 500px;
  overflow: auto;
}

.json-content {
  margin: 0;
  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.6;
  color: #2d3b5d;
  white-space: pre-wrap;
  word-wrap: break-word;
}

.json-display::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.json-display::-webkit-scrollbar-thumb {
  background: rgba(31, 42, 68, 0.2);
  border-radius: 4px;
}

.json-display::-webkit-scrollbar-thumb:hover {
  background: rgba(31, 42, 68, 0.3);
}

.slot-dialog__section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.slot-dialog__section h3 {
  font-size: 15px;
  font-weight: 600;
  color: #2d3b5d;
}

.empty-hint {
  font-size: 13px;
  color: #98a2be;
}

.code-list :deep(.el-tag) {
  font-size: 13px;
  padding: 6px 10px;
  cursor: move;
  transition: all 0.2s ease;
}

.code-list :deep(.el-tag:hover) {
  transform: scale(1.02);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

.code-list :deep(.sortable-ghost) {
  opacity: 0.5;
}

.code-list :deep(.sortable-drag) {
  opacity: 0.8;
}

.draggable-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.draggable-tag.no-drag {
  cursor: default;
}

.drag-handle {
  cursor: grab;
  color: #909399;
  font-size: 14px;
  user-select: none;
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

.drag-handle:active {
  cursor: grabbing;
}

.login-hint {
  margin-top: 12px;
  padding: 8px 12px;
  background: #fff7e6;
  border: 1px solid #ffe58f;
  border-radius: 4px;
  font-size: 13px;
  text-align: center;
}

.tag-content {
  cursor: pointer;
  user-select: none;
  transition: all 0.2s ease;
  padding: 2px 4px;
  border-radius: 4px;
  margin: -2px -4px;
}

.tag-content:hover {
  background: rgba(63, 106, 216, 0.15);
  transform: scale(1.05);
}

.code-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 8px;
}

.code-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  border-radius: 10px;
  background: rgba(63, 106, 216, 0.1);
  color: #2d3b5d;
}

.code-item__remove {
  border: none;
  background: rgba(231, 76, 60, 0.12);
  color: #aa3c2b;
  border-radius: 8px;
  padding: 4px 8px;
  cursor: pointer;
  font-size: 12px;
}

.code-item__remove:hover {
  background: rgba(231, 76, 60, 0.2);
}

.add-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.form-submit {
  width: 100%;
}

.form-feedback {
  font-size: 13px;
  padding: 8px 12px;
  border-radius: 10px;
}

.form-feedback.is-success {
  background: rgba(46, 204, 113, 0.12);
  color: #21744a;
}

.form-feedback.is-warning {
  background: rgba(241, 196, 15, 0.18);
  color: #8a6d1d;
}

.form-feedback.is-error {
  background: rgba(231, 76, 60, 0.12);
  color: #b63f2d;
}

@media (min-width: 1024px) {
.shelf-stack__col {
  min-width: 500px;
  display: flex;
}
}

@media (max-width: 1024px) {
  .search-panel {
    padding: 18px;
  }

.shelf-stack__col {
  min-width: 400px;
  display: flex;
}

  .shelf-side__levels {
    grid-template-columns: repeat(1, minmax(0, 1fr));
  }

  .slot-dialog {
    max-height: 96vh;
  }

  .slot-dialog__header {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }

  .dialog-close {
    align-self: flex-end;
  }
}

@media (max-width: 560px) {
  .page {
    padding: 16px;
  }

  .shelf-stack__col {
    min-width: 280px;
    display: flex;
  }

  .shelf-grid {
    grid-template-columns: repeat(1, minmax(0, 1fr));
  }
  
  /* 手机竖屏时，进一步优化编号显示 */
  .shelf-level__codes li {
    font-size: 12px;
    padding: 3px 8px;
    line-height: 1.2em;
  }
  
  /* 小屏手机时，重新计算3行高度（因为字体和padding更小：font-size 12px, padding 3px 8px） */
  .shelf-level__codes {
    max-height: calc(3 * (12px * 1.2 + 6px + 6px)); /* 3行：(字体高度 + padding上下 + gap) */
  }
  
  .shelf-card__body :deep(.shelf-level) {
    padding: 10px 12px;
  }
}
</style>
